
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <apr-1/apr_date.h>
#include <apr-1/apr_file_io.h>
#include <apr-1/apr_strings.h>
#include "../iface/APRInterface.h"
#include "../iface/debug.h"
#include "StringUtils.h"

/** Get the current time as an RFC822 format date/time string. The caller
 * assumes responsibility for freeing this string from the pointer.
 * 
 * @return
 * The current date and time as a string. */
char *StringUtils::getDateTimeNowAsString()
{
   char timebuf[APR_RFC822_DATE_LEN + 1];
   apr_status_t status;
   apr_time_t timet = apr_time_now();
   status = apr_rfc822_date(timebuf, timet);
   CheckAPRError(status);
   if (status != APR_SUCCESS) return NULL;
   return strdup(timebuf);
}

/** Get the current time with the given duration added to it as a RFC822
 * format date/time string. The caller assumes responsibility for
 * freeing this string from the pointer.
 * 
 * @param secondstime
 * How many seconds in the future this date is returned as.
 * 
 * @return
 * The future date and time as a string. */
char *StringUtils::getDateTimeNowPlusThisMuchAsString(int secondstime)
{
   char timebuf[APR_RFC822_DATE_LEN + 1];
   apr_status_t status;
   apr_time_t timet = apr_time_now() + APR_USEC_PER_SEC * secondstime;
   status = apr_rfc822_date(timebuf, timet);
   CheckAPRError(status);
   if (status != APR_SUCCESS) return NULL;
   return strdup(timebuf);
}

/** Read in a file and place it in an allocated block of memory as a string.
 * The caller inherits the responsibility of freeing the pointer returned.
 * 
 * @param[out] fstring
 * A pointer to the place when the pointer to the string can be put.
 * 
 * @param filename
 * The name of the file to load into the string.
 * 
 * @param mp
 * The APR memory pool for allocating file reading structures.
 * 
 * @return
 * Zero on success, non-zero on failure. */
int StringUtils::readFileToString(char **fstring, const char *filename, apr_pool_t *mp)
{
   if ((filename == NULL) || (strlen(filename) == 0))
   {
      errprint("cannot read from %s filename", (filename) ? "an empty" : "a NULL");
      return -1;
   }
   if (fstring == NULL)
   {
      errprint("nowhere to put the output from the file '%s'", filename);
      return -1;
   }
   if (mp == NULL)
   {
      errprint("this function requires a non-NULL memory pool");
      return -1;
   }
   apr_file_t *newFP;
   apr_status_t status;
   status = apr_file_open(&newFP, filename, APR_FOPEN_CREATE|APR_FOPEN_BINARY|APR_FOPEN_READ|APR_FOPEN_BUFFERED, APR_FPROT_OS_DEFAULT, mp);
   CheckAPRError(status);
   apr_finfo_t finfo;
#ifdef USE_STAT_FILE_SIZE
   status = apr_file_info_get(&finfo, APR_FINFO_SIZE, newFP);
   CheckAPRError(status);
#else
   apr_off_t offset = 0;
   status = apr_file_seek(newFP, APR_END, &offset);
   CheckAPRError(status);
   finfo.size = offset;
   offset = 0;
   status = apr_file_seek(newFP, APR_SET, &offset);
   CheckAPRError(status);
#endif
   apr_size_t fullread = finfo.size;
   char *tstring = (char *)malloc(sizeof(char)*(finfo.size+1));
   status = apr_file_read_full(newFP, tstring, finfo.size, &fullread);
   CheckAPRError(status);
   tstring[fullread] = '\0';
   if (fullread != finfo.size)
   {
      errprint("reading string from '%s' - expected to read %d bytes, actually read %d", filename, finfo.size, fullread);
      status = apr_file_close(newFP);
      CheckAPRError(status);
      free(tstring);
      *fstring = NULL;
      return -1;
   }
   status = apr_file_close(newFP);
   CheckAPRError(status);
   *fstring = tstring;
   return 0;
}

/** Write out the contents of a string into a file.
 * 
 * @param fstring
 * The string to write out.
 * 
 * @param filename
 * The name of the file to write the string into.
 * 
 * @param mp
 * The APR memory pool to use for file desciprtor allocation etc.
 * 
 * @return
 * Zero on success, non-zero on failure. */
int StringUtils::writeStringToFile(const char *fstring, const char *filename, apr_pool_t *mp)
{
   if (fstring == NULL)
   {
      errprint("writing out a NULL string does not make sense");
      return -1;
   }
   if ((filename == NULL) || (strlen(filename) == 0))
   {
      errprint("cannot write to %s filename", (filename) ? "an empty" : "a NULL");
      return -1;
   }
   if (mp == NULL)
   {
      errprint("this function requires a non-NULL memory pool");
      return -1;
   }
   apr_file_t *newFP;
   apr_status_t status;
   status = apr_file_open(&newFP, filename, APR_FOPEN_CREATE|APR_FOPEN_TRUNCATE|APR_FOPEN_BINARY|APR_FOPEN_WRITE|APR_FOPEN_BUFFERED, APR_FPROT_OS_DEFAULT, mp);
   CheckAPRError(status);
   apr_size_t datawritten = strlen(fstring);
   int textlen = datawritten;
   status = apr_file_write_full(newFP, fstring, strlen(fstring), &datawritten);
   CheckAPRError(status);
   if (datawritten != textlen)
   {
      errprint("writing string to '%s' - expected to write %d bytes, actually wrote %d", filename, textlen, datawritten);
      status = apr_file_close(newFP);
      CheckAPRError(status);
      return -1;
   }
   status = apr_file_close(newFP);
   CheckAPRError(status);
   return 0;
}

/** Format string printf-style allocation and string creation using
 * the memory pool system of APR. Variadic arguments enable printf
 * format strings to be used as-is.
 * 
 * @param mp
 * Memory pool to allocate the new string from.
 * 
 * @param format
 * The format string for the printed string.
 * 
 * @return
 * The new string. The caller does not assume responsibility as a memory
 * pool is used instead. */
char *StringUtils::mpprintf(apr_pool_t *mp, const char *format, ...)
{
   va_list args;
   va_list newcopy;
   va_start(args, format); va_copy(newcopy, args);
   char *tret = StringUtils::mpprintf(mp, format, newcopy);
   va_end(newcopy); va_end(args);
   return tret;
}

/** Format string printf-style allocation and string creation using
 * the memory pool system of APR. Backend to the variadic call.
 * 
 * @param mp
 * Memory pool to allocate the new string from.
 * 
 * @param format
 * The format string for the printed string.
 * 
 * @param args
 * Variadic argument list of data.
 * 
 * @return
 * The new string. The caller does not assume responsibility as a memory
 * pool is used instead. */
char *StringUtils::mpprintf(apr_pool_t *mp, const char *format, va_list args)
{
   char room;
   va_list beginagain;
   va_copy(beginagain, args);
#ifdef USE_APR_VSNPRINTF
   int n = apr_vsnprintf(&room, 0, format, beginagain);
#else
   int n = vsnprintf(&room, 0, format, beginagain);
#endif
   va_end(beginagain);
   char *actualspace = (char *)apr_palloc(mp, sizeof(char)*(n+2));
   va_copy(beginagain, args);
#ifdef USE_APR_VSNPRINTF
   apr_vsnprintf(actualspace, n+1, format, beginagain);
#else
   vsnprintf(actualspace, n+1, format, beginagain);
#endif
   va_end(beginagain);
   return actualspace;
}

/** Format string printf-style allocation and string creation using
 * the normal malloc() call. The caller must assume responsibility
 * for properly disposing of the created string. Variadic arguments
 * enable printf format strings to be used as-is.
 * 
 * @param format
 * The format string for the printed string.
 * 
 * @return
 * The new string. The caller must assume responsibility for the
 * destruction of this string. */
char *StringUtils::varprintf(const char *format, ...)
{
   va_list args;
   va_list newcopy;
   va_start(args, format); va_copy(newcopy, args);
   char *tret = StringUtils::varprintf(format, newcopy);
   va_end(newcopy); va_end(args);
   return tret;
}

/** Format string printf-style allocation and string creation using
 * the normal malloc() call. The caller must assume responsibility
 * for properly disposing of the created string. Backend to the
 * variadic call.
 * 
 * @param format
 * The format string for the printed string.
 * 
 * @param args
 * Variadic argument list of data.
 * 
 * @return
 * The new string. The caller must assume responsibility for the
 * destruction of this string. */
char *StringUtils::varprintf(const char *format, va_list args)
{
   char room;
   va_list beginagain;
   va_copy(beginagain, args);
#ifdef USE_APR_VSNPRINTF
   int n = apr_vsnprintf(&room, 0, format, beginagain);
#else
   int n = vsnprintf(&room, 0, format, beginagain);
#endif
   va_end(beginagain);
   char *actualspace = (char *)malloc(sizeof(char)*(n+5));
   va_copy(beginagain, args);
#ifdef USE_APR_VSNPRINTF
   apr_vsnprintf(actualspace, n+4, format, beginagain);
#else
   vsnprintf(actualspace, n+4, format, beginagain);
#endif
   va_end(beginagain);
   return actualspace;
}

/** Takes a list of string in an std::vector and disposes of
 * each with free, before deleting the container.
 * 
 * @param strlist
 * The string list to be disposed of. */
void StringUtils::destroyStringList(std::vector<char *> *strlist)
{
   if (strlist == NULL) return;
   for (int i = 0; i < (int)(strlist->size()); i++)
      if (strlist->at(i)) free(strlist->at(i));
   delete strlist;
}

/** Takes the items in an std::vector string list and concatenates
 * them all into one big string and returns it. The caller assumes
 * responsibility for destroying the resulting string afterwards.
 * 
 * @param strlist
 * A list of strings to flatten into one string.
 * 
 * @return
 * The new string which is the concatenation of all the strings in
 * the list. The caller must dispose of the string properly. */
char *StringUtils::flattenStringList(std::vector<char *> *strlist)
{
   return flattenStringList(strlist, NULL);
}

/** Takes the items in an std::vector string list and concatenates
 * them all into one big string with separators and returns it.
 * The caller assumes responsibility for destroying the resulting
 * string afterwards.
 * 
 * @param strlist
 * A list of strings to flatten into one string.
 * 
 * @param sep
 * A seperator string to put between the strings in the string list
 * when concatenating them together.
 * 
 * @return
 * The new string which is the concatenation of all the strings in
 * the list. The caller must dispose of the string properly. */
char *StringUtils::flattenStringList(std::vector<char *> *strlist, const char *sep)
{
   int total_length = 0;
   for (int i = 0; i < (strlist->size()); i++)
   {
      total_length += strlen(strlist->at(i));
      total_length += ((sep) ? strlen(sep) : 0);
   }
   int total_length2 = 0;
   char *cbuffer = (char *)malloc(sizeof(char)*(total_length+1));
   for (int i = 0; i < (strlist->size()); i++)
   {
      int thissize = strlen(strlist->at(i));
      memcpy(&(cbuffer[total_length2]), strlist->at(i), sizeof(char)*thissize);
      total_length2 += thissize;
      if (sep)
      {
         int thissize2 = strlen(sep);
         memcpy(&(cbuffer[total_length2]), sep, sizeof(char)*thissize2);
         total_length2 += thissize2;
      }
   }
   cbuffer[total_length] = '\0';
   return cbuffer;
}

/** Sanitise a string in-place by replacing characters via this lookup
 * table. Anything in the string given that is not in the lookup table
 * is replaced with an underscore (_).
 * 
 * @param[in,out] tstring
 * String to sanitise in-place. */
void StringUtils::aggressiveStringSanitiser(char *tstring)
{
   const char modascii[96] = { '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '+', '_', '-', '.', '_',
                               '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '_', '_', '_', '_', '_',
                               '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
                               'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', '_', '_', '_', '_',
                               '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
                               'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '_', '_', '_', '_', '_' };
   int stlen = strlen(tstring);
   for (int i = 0; i < stlen; i++)
      tstring[i] = (tstring[i] > 32) ? modascii[tstring[i] - 32] : '_';
}

/** Intersect two comma-separated lists and return the comma-separated
 * list that contains only the common elements between them. This
 * returned list must be freed by the caller later.
 * 
 * @param csl1
 * The first comma-separated list.
 * 
 * @param csl2
 * The second comma-separated list.
 * 
 * @return
 * A new list, that contains the elements that both lists have in common.
 * The onus for freeing this lies with the caller. */
char *StringUtils::intersectCSL(const char *csl1, const char *csl2)
{
   if ((csl1 == NULL) || (strlen(csl1) == 0)) return NULL;
   if ((csl2 == NULL) || (strlen(csl2) == 0)) return NULL;
   char *tcsl1 = (char *)malloc(sizeof(char)*(strlen(csl1)+1));
   strcpy(tcsl1, csl1);
   char *tcsl2 = (char *)malloc(sizeof(char)*(strlen(csl2)+1));
   strcpy(tcsl2, csl2);
   char *varplist = (char *)malloc(sizeof(char));
   varplist[0] = '\0';
   char *remainder1 = NULL;
   char *remainder2 = NULL;
   char *ttoken1 = apr_strtok(tcsl1, ",", &remainder1);
   while (ttoken1 != NULL)
   {
      strcpy(tcsl2, csl2);
      char *ttoken2 = apr_strtok(tcsl2, ",", &remainder2);
      while (ttoken2 != NULL)
      {
         if (strcmp(ttoken1, ttoken2) == 0)
         {
            char *oldvarplist = varplist;
            varplist = StringUtils::varprintf((strlen(oldvarplist) > 0) ? "%s,%s" : "%s%s", oldvarplist, ttoken1);
            free(oldvarplist);
            break;
         }
         ttoken2 = apr_strtok(NULL, ",", &remainder2);
      }
      ttoken1 = apr_strtok(NULL, ",", &remainder1);
   }
   free(tcsl1);
   free(tcsl2);
   return varplist;
}

/** Find the differences between two comma-separated lists and
 * render these differences as a comma-separated list that contains
 * the elements that are not in either list. This list must be freed
 * by the caller.
 * 
 * @param csl1
 * The first comma-separated list.
 * 
 * @param csl2
 * The second comma-separated list.
 * 
 * @return
 * The list of all elements that are not in either list. The list that
 * is returned must be later freed by the caller. */
char *StringUtils::differenceCSL(const char *csl1, const char *csl2)
{
   char *varplist = (char *)malloc(sizeof(char));
   varplist[0] = '\0';
   if ((csl1 == NULL) || (strlen(csl1) == 0))
   {
      if (csl2 == NULL)
         return NULL;
      else
      {
         char *oldvarplist = varplist;
         varplist = StringUtils::varprintf((strlen(oldvarplist) > 0) ? "%s,%s" : "%s%s", oldvarplist, csl2);
         free(oldvarplist);
      }
      return varplist;
   }
   if ((csl2 == NULL) || (strlen(csl2) == 0))
   {
      if (csl1 == NULL)
         return NULL;
      else
      {
         char *oldvarplist = varplist;
         varplist = StringUtils::varprintf((strlen(oldvarplist) > 0) ? "%s,%s" : "%s%s", oldvarplist, csl1);
         free(oldvarplist);
      }
      return varplist;
   }
   char *tcsl1 = (char *)malloc(sizeof(char)*(strlen(csl1)+1));
   strcpy(tcsl1, csl1);
   char *tcsl2 = (char *)malloc(sizeof(char)*(strlen(csl2)+1));
   strcpy(tcsl2, csl2);
   char *remainder1 = NULL;
   char *remainder2 = NULL;
   char *ttoken1 = apr_strtok(tcsl1, ",", &remainder1);
   while (ttoken1 != NULL)
   {
      strcpy(tcsl2, csl2);
      char *ttoken2 = apr_strtok(tcsl2, ",", &remainder2);
      int all_nonmatching = 1;
      while (ttoken2 != NULL)
      {
         if (strcmp(ttoken1, ttoken2) == 0) all_nonmatching = 0;
         ttoken2 = apr_strtok(NULL, ",", &remainder2);
      }
      if (all_nonmatching)
      {
         char *oldvarplist = varplist;
         varplist = StringUtils::varprintf((strlen(oldvarplist) > 0) ? "%s,%s" : "%s%s", oldvarplist, ttoken1);
         free(oldvarplist);
      }
      ttoken1 = apr_strtok(NULL, ",", &remainder1);
   }
   free(tcsl1);
   free(tcsl2);
   return varplist;
}

/** This is a wrapper around strcmp that allows string comparison which
 * enables quicksorting with the c standard librar function 'qsort'.
 * 
 * @param c1
 * First string to compare as a void pointer.
 * 
 * @param c2
 * Second string to compare as a void pointer.
 * 
 * @return
 * An integer signifying ordinal direction. */
int StringUtils::qsort_strcmp_wrapper(const void *c1, const void *c2)
{
   return strcmp(*((char **)c1), *((char **)c2));
}

/** A check to see if the capabilities offered by the compute are enough to
 * satisfy the requirements of the work.
 * 
 * @param capswork
 * The comma-separated list of capabilities required by the work.
 * 
 * @param capscompute
 * The comma-separated list of capabilities advertised by the compute.
 * 
 * @return
 * Non-zero for true/success (so capabilities are acceptable), zero for
 * false/failure (capabilities unacceptable). */
int StringUtils::areCapabilitiesAcceptable(char *capswork, char *capscompute)
{
   // Get intersection of two lists
   // Build new list1 from the things not in the intersection
   // Build new list2 from the things not in the intersection
   // Sort both lists alphabetically.
   // Step through the lists making sure that list1 is a subset of list2
   // If so, accept possibility, if not reject
   char *isect = intersectCSL(capswork, capscompute);
   char *diff_work = differenceCSL(capswork, isect);
   char *diff_compute = differenceCSL(capscompute, isect);
   char *ttoken, *remainder;
   free(isect);
   if ((diff_work == NULL) || (diff_compute == NULL))
   {
      if (diff_work) free(diff_work);
      if (diff_compute) free(diff_compute);
      return (!((diff_work != NULL) || (diff_compute == NULL)));
   }
   remainder = NULL;
   std::vector<char *> *capswork_diff = new std::vector<char *>();
   ttoken = apr_strtok(diff_work, ",", &remainder);
   while (ttoken != NULL)
   {
      capswork_diff->push_back(ttoken);
      ttoken = apr_strtok(NULL, ",", &remainder);
   }
   if (capswork_diff->size() == 0)
   {
      free(diff_work);
      free(diff_compute);
      delete capswork_diff;
      return 1;
   }
   remainder = NULL;
   std::vector<char *> *capscompute_diff = new std::vector<char *>();
   ttoken = apr_strtok(diff_compute, ",", &remainder);
   while (ttoken != NULL)
   {
      capscompute_diff->push_back(ttoken);
      ttoken = apr_strtok(NULL, ",", &remainder);
   }
   qsort(&(capswork_diff->at(0)), capswork_diff->size(), sizeof(char *), StringUtils::qsort_strcmp_wrapper);
   qsort(&(capscompute_diff->at(0)), capscompute_diff->size(), sizeof(char *), StringUtils::qsort_strcmp_wrapper);
   // We know that there is nothing the same between these two lists
   // Begin on the same index
   int *possiblefailures = (int *)malloc(sizeof(int)*capswork_diff->size());
   for (int i = 0; i < capswork_diff->size(); i++) possiblefailures[i] = 1;
   int i = 0, j = 0;
   for ( ; ((i < capswork_diff->size()) && (j < capscompute_diff->size())) ; )
   {
      // First, copy both
      char *work_candidate = (char *)malloc(sizeof(char)*(strlen(capswork_diff->at(i))+1));
      strcpy(work_candidate, capswork_diff->at(i));
      char *compute_candidate = (char *)malloc(sizeof(char)*(strlen(capscompute_diff->at(j))+1));
      strcpy(compute_candidate, capscompute_diff->at(j));
      char *rem_work = NULL;
      char *key_work = apr_strtok(work_candidate, "=", &rem_work);
      char *value_work = apr_strtok(NULL, "=", &rem_work);
      char *rem_compute = NULL;
      char *key_compute = apr_strtok(compute_candidate, "=", &rem_compute);
      char *value_compute = apr_strtok(NULL, "=", &rem_compute);
      if (strcmp(key_work, key_compute) == 0)
      {
         // Only succeed if the work demands less or equal to the resource that the compute can provide!
         if (atoi(value_work) <= atoi(value_compute))
         {
            possiblefailures[i] = 0;
         }
      }
      int tval = strcmp(capswork_diff->at(i), capscompute_diff->at(j));
      (tval > 0) ? j++ : i++;
      free(work_candidate);
      free(compute_candidate);
   }
   int wasfailure = 0;
   for (int i = 0; i < capswork_diff->size(); i++)
      wasfailure |= possiblefailures[i];
   free(diff_work);
   free(diff_compute);
   delete capswork_diff;
   delete capscompute_diff;
   return (!(wasfailure));
}

/** Destroy a string completely.
 * 
 * @param stringpntr
 * A pointer to the string pointer to be destroyed. */
void StringUtils::razeString(char **stringpntr)
{
   char *oldstring = *stringpntr;
   if (oldstring == NULL) return;
   memset(oldstring, 0, sizeof(char)*strlen(oldstring));
   *stringpntr = NULL;
   free(oldstring);
}
